/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package esnipe2;

import java.awt.EventQueue;
import java.util.Calendar;
import java.util.TimeZone;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 *
 * @author hangsu
 */
public class ItemRunnable implements Runnable {

    /**
     * Constructor, wrapper of EbayItem for multi-thread usage
     * @param item the EBAY item managed by this object
     * @param api the API used for retrieving information from web page
     * @param gui GUI component used for managing this object
     */
    public ItemRunnable(final EbayItem item, final EbayAPI api, final UpdateGUI gui) {
        /*item.status should not be RESULT_UNKNOWN*/
        assert item.status != ItemResult.RESULT_UNKNOWN;
        this.state = ItemStatus.STATUS_INIT;
        this.item = item;
        this.api = api;
        this.gui = gui;
        this.statusLock = new ReentrantLock();
        this.lastupdateTime = Calendar.getInstance(TimeZone.getTimeZone("PST"));
        this.snipeGap = Constants.snipe_gap;
        //TODO: this should be used after getItemInfo is implemented
        /*if (!this.setSnipeGap(1)) {
            this.state = ItemStatus.STATUS_ERROR;
        }*/
        EventQueue.invokeLater(new Runnable() {

            @Override
            public void run() {
                gui.updateID(item.itemUrl, item.itemNo);
                gui.updateName(item.itemNo, item.itemName);
                gui.updateCurPrice(item.itemNo, item.currentPrice);
                gui.updateStatus(item.itemNo, state);
                //gui.updateEndTime(item.itemNo, item.endTime);
                gui.updateLastTime(item.itemNo, lastupdateTime);
            }
        });
    }

    /**
     * main logic here
     */
    @Override
    public void run() {

        long sleeptime = 1000;

        try {
            while (true) {
                this.statusLock.lock();
                try {
                    final Calendar currentTime = Calendar.getInstance(TimeZone.getTimeZone("PST"));
                    /* 
                     * - STATUS_ERROR
                     * - STATUS_CLOSED
                     * - STATUS_TIMEOUT
                     * - STATUS_WON
                     * no need to run anymore in these 4 status
                     */
                    if (this.state == ItemStatus.STATUS_ERROR
                            || this.state == ItemStatus.STATUS_CLOSED
                            || this.state == ItemStatus.STATUS_TIMEOUT
                            || this.state == ItemStatus.STATUS_WON) {
                        break;
                    }
                    /*
                     * - STATUS_INIT
                     * - STATUS_OVERTAKEN
                     * waiting for user setting in these 2 status, NO web interaction
                     */
                    if (this.state == ItemStatus.STATUS_INIT
                            || this.state == ItemStatus.STATUS_OVERTAKEN) {
                        if (currentTime.after(this.item.endTime)) {
                            this.state = ItemStatus.STATUS_TIMEOUT;
                            this.item.status = ItemResult.RESULT_LOST;
                            EventQueue.invokeLater(new Runnable() {

                                @Override
                                public void run() {
                                    gui.updateStatus(item.itemNo, state);
                                }
                            });
                            break;
                        }
                        sleeptime = 1000;
                    } 
                    /*
                     * - STATUS_SUSPENDED
                     */
                    else if (this.state == ItemStatus.STATUS_SUSPENDED) {
                        if (currentTime.after(this.item.endTime)) {
                            this.state = ItemStatus.STATUS_TIMEOUT;
                            this.item.status = ItemResult.RESULT_LOST;
                            EventQueue.invokeLater(new Runnable() {

                                @Override
                                public void run() {
                                    gui.updateStatus(item.itemNo, state);
                                }
                            });
                            break;
                        }
                        final Pricepair currentprice = this.api.getCurrentPrice(this.item.itemUrl);
                        if (currentprice.valid) {
                            if (currentprice.currentPrice < this.item.currentPrice.currentPrice) {
                                this.state = ItemStatus.STATUS_ERROR;
                                EventQueue.invokeLater(new Runnable() {

                                    @Override
                                    public void run() {
                                        gui.updateStatus(item.itemNo, state);
                                    }
                                });
                                break;
                            }
                            EventQueue.invokeLater(new Runnable() {

                                @Override
                                public void run() {
                                    gui.updateLastTime(item.itemNo, currentTime);
                                    gui.updateCurPrice(item.itemNo, currentprice);
                                }
                            });
                            if (currentprice.nextPrice > this.myPrice) {
                                this.state = ItemStatus.STATUS_OVERTAKEN;
                                EventQueue.invokeLater(new Runnable() {

                                    @Override
                                    public void run() {
                                        gui.updateStatus(item.itemNo, state);
                                    }
                                });
                            }
                            this.item.currentPrice = currentprice;
                            this.lastupdateTime = currentTime;
                        }
                        sleeptime = 1000;
                    } 
                    /*
                     * - STATUS_RUN
                     */
                    else if (this.state == ItemStatus.STATUS_RUN) {
                        if (currentTime.after(this.myBiddingtime)) {
                            boolean success = this.api.placeBid(this.item.itemUrl, this.item.currentPrice.nextPrice);
                            if (success) {
                                final Pricepair currentprice = this.api.getCurrentPrice(this.item.itemUrl);
                                this.state = ItemStatus.STATUS_LEADING;
                                this.lastupdateTime = currentTime;
                                if(currentprice.valid)
                                    this.item.currentPrice.currentPrice = currentprice.currentPrice;
                                EventQueue.invokeLater(new Runnable() {

                                    @Override
                                    public void run() {
                                        gui.updateStatus(item.itemNo, state);
                                        if(currentprice.valid)
                                            gui.updateCurPrice(item.itemNo, currentprice);
                                        gui.updateLastTime(item.itemNo, currentTime);
                                    }
                                });
                            }
                        } else {
                            final Pricepair currentprice = this.api.getCurrentPrice(this.item.itemUrl);
                            if (currentprice.valid) {
                                if (currentprice.currentPrice < this.item.currentPrice.currentPrice) {
                                    this.state = ItemStatus.STATUS_ERROR;
                                    EventQueue.invokeLater(new Runnable() {

                                        @Override
                                        public void run() {
                                            gui.updateStatus(item.itemNo, state);
                                        }
                                    });
                                    break;
                                }
                                EventQueue.invokeLater(new Runnable() {

                                    @Override
                                    public void run() {
                                        gui.updateLastTime(item.itemNo, currentTime);
                                        gui.updateCurPrice(item.itemNo, currentprice);
                                    }
                                });
                                if (currentprice.nextPrice > this.myPrice) {
                                    this.state = ItemStatus.STATUS_OVERTAKEN;
                                    EventQueue.invokeLater(new Runnable() {

                                        @Override
                                        public void run() {
                                            gui.updateStatus(item.itemNo, state);
                                        }
                                    });
                                }
                                this.item.currentPrice = currentprice;
                                this.lastupdateTime = currentTime;
                            }
                        }
                        sleeptime = 100;
                    }
                    /*
                     * - STATUS_LEADING
                     */
                    else if (this.state == ItemStatus.STATUS_LEADING) {
                        Calendar lateTime = (Calendar)this.item.endTime.clone();
                        /*try for 10 seconds before finally give up checking result*/
                        lateTime.add(Calendar.SECOND, 10);
                        if(currentTime.after(lateTime)) {
                            this.state = ItemStatus.STATUS_ERROR;
                            this.item.status = ItemResult.RESULT_UNKNOWN;
                            EventQueue.invokeLater(new Runnable() {

                                @Override
                                public void run() {
                                    gui.updateStatus(item.itemNo, state);
                                }
                            });
                            break;
                        }
                        ItemResult r = api.getResult(this.item.itemUrl);
                        final Pricepair currentprice = api.getCurrentPrice(this.item.itemUrl);
                        this.item.status = r;
                        if (r == ItemResult.RESULT_WON) {
                            this.state = ItemStatus.STATUS_WON;
                            this.lastupdateTime = currentTime;
                            if (currentprice.valid) {
                                this.item.currentPrice = currentprice;
                            }
                            EventQueue.invokeLater(new Runnable() {

                                @Override
                                public void run() {
                                    gui.updateStatus(item.itemNo, state);
                                    gui.updateLastTime(item.itemNo, currentTime);
                                    if (currentprice.valid) {
                                        gui.updateCurPrice(item.itemNo, currentprice);
                                    }
                                }
                            });
                        } else if (r == ItemResult.RESULT_LOST) {
                            this.state = ItemStatus.STATUS_CLOSED;
                            this.lastupdateTime = currentTime;
                            if (currentprice.valid) {
                                this.item.currentPrice = currentprice;
                            }
                            EventQueue.invokeLater(new Runnable() {

                                @Override
                                public void run() {
                                    gui.updateStatus(item.itemNo, state);
                                    gui.updateLastTime(item.itemNo, currentTime);
                                    if (currentprice.valid) {
                                        gui.updateCurPrice(item.itemNo, currentprice);
                                    }
                                }
                            });
                        } else if (r == ItemResult.RESULT_NOTEND) {
                            if (currentprice.valid) {
                                this.item.currentPrice = currentprice;
                                this.lastupdateTime = currentTime;
                                if (currentprice.currentPrice > this.myPrice) {
                                    this.state = ItemStatus.STATUS_OVERTAKEN;
                                    EventQueue.invokeLater(new Runnable() {

                                        @Override
                                        public void run() {
                                            gui.updateStatus(item.itemNo, state);
                                        }
                                    });
                                }
                                EventQueue.invokeLater(new Runnable() {

                                    @Override
                                    public void run() {
                                        gui.updateLastTime(item.itemNo, currentTime);
                                        gui.updateCurPrice(item.itemNo, currentprice);
                                    }
                                });
                            }
                        }
                        sleeptime = 100;
                    }
                    /*
                     * no other status
                     */
                    else {
                        this.state = ItemStatus.STATUS_ERROR;
                        EventQueue.invokeLater(new Runnable() {

                            @Override
                            public void run() {
                                gui.updateStatus(item.itemNo, state);
                            }
                        });
                        break;
                    }
                } finally {
                    this.statusLock.unlock();
                }
                Thread.sleep(sleeptime);
            }
        } catch (InterruptedException e) {
            System.out.print(e.toString() + "\n");
            this.state = ItemStatus.STATUS_ERROR;
            EventQueue.invokeLater(new Runnable() {

                @Override
                public void run() {
                    gui.updateStatus(item.itemNo, state);
                }
            });
        }
    }

    /**
     * suspend the agent, will not bid anymore (but still watching)
     * @return 
     */
    public boolean suspend() {
        EventQueue.invokeLater(new Runnable() {

            @Override
            public void run() {
                statusLock.lock();
                try {
                    if (state == ItemStatus.STATUS_RUN) {
                        state = ItemStatus.STATUS_SUSPENDED;
                        gui.updateStatus(item.itemNo, state);
                    }
                } finally {
                    statusLock.unlock();
                }

            }
        });
        return true;
    }

    /**
     * resume the agent, ready to bid
     * @return 
     */
    public boolean resume() {
        EventQueue.invokeLater(new Runnable() {

            @Override
            public void run() {
                statusLock.lock();
                try {
                    if (state == ItemStatus.STATUS_SUSPENDED) {
                        state = ItemStatus.STATUS_RUN;
                        gui.updateStatus(item.itemNo, state);
                    }
                } finally {
                    statusLock.unlock();
                }

            }
        });
        return true;
    }

    /**
     * try to recover after errors (called when STATUS_ERROR)
     * @return 
     */
    public boolean retry() {
        EventQueue.invokeLater(new Runnable() {

            @Override
            public void run() {
                statusLock.lock();
                try {
                    if (state != ItemStatus.STATUS_ERROR) {
                        return;
                    }
                    state = ItemStatus.STATUS_SUSPENDED;
                    gui.updateStatus(item.itemNo, state);
                    /*not sure if this works*/
                    Thread t = new Thread(ItemRunnable.this);
                    t.start();
                } finally {
                    statusLock.unlock();
                }

            }
        });
        return true;
    }

    /**
     * shutdown the agent, no chance to revive!
     */
    public void close() {
        EventQueue.invokeLater(new Runnable() {

            @Override
            public void run() {
                statusLock.lock();
                try {
                    state = ItemStatus.STATUS_CLOSED;
                    gui.updateStatus(item.itemNo, state);

                } finally {
                    statusLock.unlock();
                }

            }
        });
    }

    /**
     * Set target highest price
     * Successful call returns true, and set item's status to STATUS_SUSPENDED 
     * if its original status is STATUS_INIT or STATUS_OVERTAKEN
     * @param price user's highest acceptable price
     */
    public boolean setUserPrice(final double price) {
        EventQueue.invokeLater(new Runnable() {

            @Override
            public void run() {
                statusLock.lock();
                try {
                    if (state == ItemStatus.STATUS_ERROR
                            || item.currentPrice.nextPrice >= price) {
                        /*just want to unlock, not a good practice?*/
                        return;
                    }
                    myPrice = price;
                    gui.updateUserPrice(item.itemNo, price);
                    if (state == ItemStatus.STATUS_INIT
                            || state == ItemStatus.STATUS_OVERTAKEN) {
                        state = ItemStatus.STATUS_SUSPENDED;
                        gui.updateStatus(item.itemNo, state);
                    }
                } finally {
                    statusLock.unlock();
                }
            }
        });

        return true;
    }

    /**
     * this is only for temporary use, after getItemInfo is supplied, this should be
     * deprecated
     * @param endtime
     * @return
     */
    public boolean setEndTime(final Calendar endtime) {
        EventQueue.invokeLater(new Runnable() {

            @Override
            public void run() {
                statusLock.lock();
                try {
                    if (state == ItemStatus.STATUS_INIT) {
                        item.endTime = endtime;
                        Calendar cal = (Calendar)endtime.clone();
                        cal.add(Calendar.SECOND, -1*Constants.snipe_gap);
                        ItemRunnable.this.myBiddingtime = cal;
                        gui.updateEndTime(item.itemNo, endtime);
                    }
                } finally {
                    statusLock.unlock();
                }
            }
        });

        return true;
    }

    /**
     * optional setting, default to be /endTime/-/snipeGap/
     * @param mytime user preference of bidding time
     * @deprecated 
     */
    public boolean setUserTime(Calendar mytime) {
        if (mytime.after(this.item.endTime)) {
            return false;
        }
        this.myBiddingtime = mytime;
        EventQueue.invokeLater(new Runnable() {

            @Override
            public void run() {
                statusLock.lock();
                try {
                    if (state == ItemStatus.STATUS_TIMEOUT) {
                        state = ItemStatus.STATUS_SUSPENDED;
                        gui.updateStatus(item.itemNo, state);
                    }
                } finally {
                    statusLock.unlock();
                }

            }
        });
        return true;
    }

    /**
     * optional setting, default to be 1 second
     * NOTE: call to this method will change /myBiddingtime/
     * @param gap integer number indicating the snipe time ahead ending time
     * @deprecated potential dead locking
     */
    public final boolean setSnipeGap(final int gap) {
        EventQueue.invokeLater(new Runnable() {

            @Override
            public void run() {
                statusLock.lock();
                try {
                    if (gap < 1 || gap > 60) {
                        return;
                    }
                    Calendar snipeTime = (Calendar) item.endTime.clone();
                    snipeTime.add(Calendar.SECOND, -1 * gap);
                    if (snipeTime.before(Calendar.getInstance(TimeZone.getTimeZone("PST")))) {
                        return;
                    }
                    snipeGap = gap;
                    myBiddingtime = snipeTime;
                } finally {
                    statusLock.unlock();
                }

            }
        });
        return true;
    }
    private EbayItem item; /*each item belongs to AT MOST one ItemRunnable*/

    private EbayAPI api;
    private UpdateGUI gui;
    private ItemStatus state;
    private Calendar lastupdateTime;
    private Lock statusLock;

    /*user settings*/
    private Calendar myBiddingtime; /*default to be /endTime/-/snipeGap/*/

    private double myPrice;
    private int snipeGap; /*default to be 1 (seconds)*/

}
